
#include "Sound.h"
#include "../Logger.h"
#include "AudioCore.h"
#include "../Globals.h"

#include <string>
#include <cstring>
#include <SDL/SDL_audio.h>
#include <iostream>

#ifdef _WIN32
#include <Vorbis/vorbisfile.h>
#else
#include <vorbis/vorbisfile.h>
#endif


namespace al
{

Sound::Sound()
{
    bufferId = 0;
}

Sound::~Sound()
{
    Release( );
}

bool Sound::SetData(ALuint buffer)
{
    if( bufferId != 0 )
        return false;

    bufferId = buffer;
    return true;
}

bool Sound::Release()
{
    AudioCore::ClearErrorBuffer( );
    alDeleteBuffers( 1, &bufferId );
    bufferId = 0;
    if( !AudioCore::Check( ) )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "alGenBuffers failed trying to create a new buffer";// << SOURCE;
        return false;
    }
    return true;
}

ALuint Sound::GetBufferId()
{
    return bufferId;
}

bool Sound::LoadWAV(const char* path)
{
    if( bufferId != 0 )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_WARNING ) << "Pre loaded sound is loading again! " << ECHO_EXP( path );
        Release( );
    }

    AudioCore::ClearErrorBuffer( );
    //create a new buffer and check if it was created properly.
    alGenBuffers( 1, &bufferId );
    if( !AudioCore::Check( ) )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "alGenBuffers failed trying to create a new buffer";// << SOURCE;
        return false;
    }

    SDL_AudioSpec wavspec;
    uint32_t wavlen;
    uint8_t* wavbuf;
    if( !SDL_LoadWAV( path, &wavspec, &wavbuf, &wavlen ) ) return false;

    // map wav header to openal format 
    ALenum format;
    switch( wavspec.format )
    {
    case AUDIO_U8:
    case AUDIO_S8: format = wavspec.channels == 2 ? AL_FORMAT_STEREO8: AL_FORMAT_MONO8;
        break;
    case AUDIO_U16:
    case AUDIO_S16: format = wavspec.channels == 2 ? AL_FORMAT_STEREO16: AL_FORMAT_MONO16;
        break;
    default: SDL_FreeWAV( wavbuf );
        return false;
    }

    AudioCore::ClearErrorBuffer( );

    alBufferData( bufferId, format, wavbuf, wavlen, wavspec.freq );
    SDL_FreeWAV( wavbuf );

    if( !AudioCore::Check( ) )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "alBufferData failed trying to fill buffer";// << SOURCE;
        return false;
    }

    return true;
}

bool Sound::LoadOGG(const char* path)
{
    FILE* oggFile;
    OggVorbis_File oggHandle;
    if( !( oggFile = fopen( path, "rb" ) ) )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "Loading ogg file failed.";// << SOURCE;
        return false;
    }

    if( ( ov_open_callbacks( oggFile, &oggHandle, NULL, 0, OV_CALLBACKS_DEFAULT ) ) < 0 )
    {
        fclose( oggFile );
        //result to string 
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "ov open callback failed.";// << SOURCE;
        return false;
    }

    // some formatting data
    vorbis_info* vorbisInfo;
    ALuint format;
    vorbisInfo = ov_info( &oggHandle, -1 );

    if( vorbisInfo->channels == 1 )
        format = AL_FORMAT_MONO16;
    else
        format = AL_FORMAT_STEREO16;

    AudioCore::ClearErrorBuffer( );
    //generate buffer id 
    alGenBuffers( 1, &bufferId );
    if( !AudioCore::Check( ) )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "alGenBuffers failed trying to create a new buffer";// << SOURCE;
        return false;
    }

    //placeholder of audio data
    char data[SINGLE_BUFFER_LIMIT];
    //the buffer to fit in all audio data
    char* buffer = NULL;
    int size = 0;
    int section;
    bool eof = false;

    //read all data
    while( !eof )
    {
        //read from vorbis file 
        int read = ov_read( &oggHandle, data, SINGLE_BUFFER_LIMIT, ENDIAN, WORD_SIZE, SGNED, &section );
        if( read == 0 )
        {
            eof = true;
        }
        else if( buffer == NULL )//if first read, initialize buffer
        {
            buffer = new char[read];
            memcpy( buffer, data, read );
            size = read;
        }
        else//extend buffer to fit in all data
        {
            char* tmp = new char[size + read];
            memcpy( tmp, buffer, size );
            memcpy( tmp + size, data, read );
            size += read;
            delete[] buffer;
            buffer = tmp;
        }
    }

    AudioCore::ClearErrorBuffer( );
    //generate buffer id 
    alBufferData( bufferId, format, buffer, size, vorbisInfo->rate );
    delete[] buffer;

    if( !AudioCore::Check( ) )
    {
        LOG( Logger::CHANNEL_AUDIO, LogFileStream::LEVEL_NOTE ) << "alBufferData failed trying to fill buffer";// << SOURCE;
        return false;
    }

    return true;
}

}
